use std::collections::HashMap;
use anyhow::{anyhow, Context};
use reqwest::{header, Response};
use reqwest::header::HeaderValue;
use crate::encrypted::token::gen_csrf_token;
use serde::{Serialize, Deserialize};
use serde_json::Value;
use scraper::{ElementRef, Html, Selector};
use serde::de::Unexpected::Str;
use urlencoding::encode;


use crate::client::const_variable::{_S, _YB_OPEN_V2_0, CSRF_TOKEN, HTTPS_WAF_COOKIE, PHPSESSID, YB_USER_AGENT};
use crate::encrypted::yb_encryptor;
use crate::encrypted::yb_encryptor::encrypt_password;

/// 记录当前登陆的进度
/// * `LoginPage(String)` 此时进入跳转页面，得到验证账号密码的网页地址
/// * `Verify(String)` 此时进入账号的验证阶段，此步骤返回一个验证的网址
/// * `Confirm` ，从上一步的网址经过一次跳转，返回的网址其中带有一个很关键的参数`verify_request`,使用此参数完成最后异步验证
#[derive(Serialize, Deserialize, Debug)]
pub enum LoginProgress {
    LoginPage(String),
    Verify(String),
    Complete
}

pub type Cookie = HashMap<String, String>;
/// 易班客户端
///
/// 实现了易班登陆和填日报的功能
///
/// 模拟易班的客户端，progress表示当前验证的进度。cookie表示得到的cookie
/// ```rust
/// let client = Client::login("****", "****").await?;
/// println!("客户端：{:#?}", client);
///
/// ```
#[derive(Serialize, Deserialize, Debug)]
pub struct Client {
    pub(crate) progress: LoginProgress,
    pub(crate) cookie: Cookie,
}

impl Client {
    pub fn get_cookie(&self) -> &Cookie {
        return &self.cookie;
    }
    pub fn new(csrf_token: &str, phpsession: &str) -> Client {
        let cookie = Cookie::from(
            [(CSRF_TOKEN.to_string(), csrf_token.to_string()),
                (PHPSESSID.to_string(), phpsession.to_string())]
        );
        Client {
            progress: LoginProgress::Complete,
            cookie,
        }
    }
    /// 登录易班
    pub async fn login(username: &str, password: &str) -> anyhow::Result<Client> {
        let mut yb_client = get_login_page().await?;
        verify_identity(username, password, &mut yb_client).await?;
        confirm_identity(&mut yb_client).await?;
        confirm_identity(&mut yb_client).await?;
        if let LoginProgress::Verify(re_url) = &yb_client.progress {
            let url = url::Url::parse(&re_url.replace("#", ""))?;
            let query: HashMap<_, _> = url.query_pairs().collect();
            let verify_request = query.get("verify_request").with_context(
                || String::from("解析URL的query参数失败")
            ).unwrap().to_string();
            let token = &yb_client.cookie.get(CSRF_TOKEN).unwrap();
            auth(
                token, &verify_request, &yb_client.cookie.to_form_string(
                    &[CSRF_TOKEN, PHPSESSID], "=", ";", "",
                ),
            ).await?;
            // let data = response.text().await?;
            // println!("返回的数据{}", data);
            Ok(yb_client)
        } else {
            Err(anyhow!("登陆失败了"))
        }
    }

}

/// 需要**5**个关键的cookie:`_S,_YB_OPEN_V2_0,https_waf_cookie,PHPSESSID,csrf_token`
/*#[derive(Serialize, Debug, Deserialize)]
struct Cookie {
    #[serde(rename = "_S")]
    _s: Option<String>,
    #[serde(rename = "_YB_OPEN_V2_0")]
    _yb_open_v2_0: Option<String>,
    #[serde(rename = "PHPSESSID")]
    phpsessid: Option<String>,
    csrf_token: Option<String>,
    https_waf_cookie: Option<String>,
}*/


/// to_form_string(self,connect:&str, division: &str)，将`Form`转变为字符串，
///
/// `connect`:连接**键**和**值**的字符，
///
/// division:不同字段和分割符
///
/// encode:是否将value进行编码，可选uri,none
///
/// 借助此trait将cookie对象转为字符串
pub trait Form {
    fn to_form_string(&self, keys: &[&str], connect: &str, division: &str, encode: &str) -> String;
}

impl Form for Cookie {
    fn to_form_string(&self, keys: &[&str], connect: &str, division: &str, encode: &str) -> String {
        let mut result = String::from("");
        self.iter().filter(
            |x| keys.contains(&&**x.0) || keys.is_empty()
        ).for_each(|x| {
            if encode == "uri" {
                result += &format!("{}{}{}{}", x.0.trim(), connect.trim(), urlencoding::encode(x.1).trim(), division.trim());
            } else {
                result += &format!("{}{}{}{}", x.0.trim(), connect.trim(), x.1.trim(), division.trim());
            }
        });
        return result;
    }
}
/*impl Cookie {
    fn new() -> Cookie {
        Cookie {
            _s: None,
            _yb_open_v2_0: None,
            phpsessid: None,
            csrf_token: None,
            https_waf_cookie: None,
        }
    }

    /// 此函数用宏实现会非常方便，但是笔者水平有限，目前不会
    fn to_form_string(self) -> String {}
}*/
fn parse_cookie_from_header_value(header_value: &HeaderValue) -> (String, String) {
    let phpsession = std::str::from_utf8(header_value.as_bytes()).unwrap();
    let cookievalue: Vec<&str> = phpsession.split(";").collect();
    let kv = cookievalue[0].split("=").collect::<Vec<&str>>();
    let k = kv[0];
    let v = kv[1];
    return (String::from(k), String::from(v));
}

async fn auth(token: &str, verify_request: &str, cookie: &str) -> anyhow::Result<Response> {
    let client = reqwest::Client::new();
    let response = client.get("https://api.uyiban.com/base/c/auth/yiban")
        .query(&[("CSRF", &token), ("verifyRequest", &verify_request)])
        .header(header::USER_AGENT, "Mozilla/5.0 (iPhone; CPU iPhone OS 14_7_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 yiban_OS/5.0")
        .header(header::COOKIE, cookie)
        .header(header::ORIGIN, "https://c.uyiban.com")
        .send()
        .await?;
    Ok(response)
}

/// 获取登陆界面的网址
async fn get_login_page() -> anyhow::Result<Client> {
    let token = gen_csrf_token();
    // println!("生成的token:{}", token);
    let response = auth(&token, "", &format!("csrf_token={}", token)).await?;
    let headers = response.headers();
    let cookie = headers.get(header::SET_COOKIE).unwrap();
    let phpsession = std::str::from_utf8(cookie.as_bytes()).unwrap();
    let cookievalue: Vec<&str> = phpsession.split(";").collect();
    let phpsession = String::from(cookievalue[0].split("=").collect::<Vec<&str>>()[1]);
    let data_str = response.text().await?;
    let result: Value = serde_json::from_str(&data_str).unwrap();
    let oauth = &result["data"]["Data"];
    let mut cookies: Cookie = Cookie::new();
    cookies.insert(CSRF_TOKEN.to_string(), token);
    cookies.insert(PHPSESSID.to_string(), phpsession);
    return Ok(Client {
        progress: LoginProgress::LoginPage(oauth.to_string()),
        cookie: cookies,
    });
}

fn uri_encode(data: &str) -> String {
    encode(data).to_string()
}

/// 传入一个登陆页面网址，验证用户名密码。将返回一个跳转的网址
async fn verify_identity<'a>(username: &str, password: &str, yb_client: &'a mut Client) -> anyhow::Result<&'a Client> {
    let current_progress = &yb_client.progress;
    return match current_progress {
        LoginProgress::LoginPage(url) => {
            // println!("跳转的网址为:{}", &url[1..url.len()-1]);
            let client = reqwest::Client::new();
            // 获取登陆界面的网页
            let response = client.get(&url[1..url.len() - 1])
                .header(header::USER_AGENT, YB_USER_AGENT)
                .header(header::COOKIE, yb_client.cookie.to_form_string(
                    &[], "=", ";", ""))
                .header(header::ORIGIN, "https://c.uyiban.com")
                .send()
                .await?;

            let set_cookies = &response.headers().get_all(header::SET_COOKIE);
            for cookie in set_cookies {
                let (k, v) = parse_cookie_from_header_value(cookie);
                yb_client.cookie.insert(k, v);
            }
            let html = &response.text()
                .await?;
            let pubic_pem = get_public_pem(html)?;
            let cookie = yb_client.cookie.to_form_string(&[_S, HTTPS_WAF_COOKIE, _YB_OPEN_V2_0], "=", ";
            ", "");
            let redirect_url = "https://f.yiban.cn/iapp7463";
            let scope = uri_encode("1,2,3,4,");
            // println!("找到public_pem：{}", pubic_pem);
            // println!("生成的cookie：{}", &cookie);
            let password_encrypted = encrypt_password(&password, &pubic_pem)?;
            let data = HashMap::from(
                [
                    ("oauth_uname", username),
                    ("oauth_upwd", &password_encrypted),
                    ("client_id", "95626fa3080300ea"),
                    ("redirect_uri", redirect_url),
                    ("state", ""),
                    ("scope", &scope),
                    ("display", "html")
                ]
            );
            // println!("发送的data：{:#?}", data);
            let res = client.post("https://oauth.yiban.cn/code/usersure")
                .header(header::USER_AGENT, YB_USER_AGENT)
                .header(header::COOKIE, &cookie)
                .form(&data)
                .send()
                .await
                .with_context(|| format!("Request `usersure` error"))?
                .text()
                .await?;
            let result: Value = serde_json::from_str(&res).unwrap();
            // println!("验证账号密码返回值:{:#?}",result);
            let redirect_url = &result["reUrl"];
            if redirect_url.to_string().contains("err") {
                return Err(anyhow!("账号和密码验证失败:{}",redirect_url));
            }
            yb_client.progress = LoginProgress::Verify(redirect_url.to_string());
            return Ok(yb_client);
        }
        _ => Err(anyhow!("invalid client"))
    };
}

/// 从网页中解析出公钥，我们将用此对密码进行加密。此函数返回公钥
fn get_public_pem(html: &str) -> anyhow::Result<String> {
    let dom = Html::parse_document(html);

    let selector = Selector::parse("#key").unwrap();
    let element = dom.select(&selector).collect::<Vec<ElementRef>>()[0];
    let public_pem = element.value().attr("value").unwrap();
    return Ok(String::from(public_pem));
}


/// 倒数第二步，确认身份，带着cookie连续访问网址，返回经过一次跳转后的url
///
async fn confirm_identity(yb_client: &mut Client) -> anyhow::Result<&Client> {
    let cookie = yb_client.cookie.to_form_string(&[], "=", ";", "");
    return match &yb_client.progress {
        LoginProgress::Verify(url) => {
            let re_url: &str;
            if url.starts_with("\"") {
                re_url = &url[1..url.len() - 1]
            } else {
                re_url = &url
            }
            // println!("将要前往:{}，携带的cookie是: {}", re_url, &cookie);
            let client = reqwest::Client::builder().redirect(reqwest::redirect::Policy::none()).build()?;
            let response = client.get(re_url)
                .header(header::COOKIE, &cookie)
                .header(header::USER_AGENT, "Mozilla/5.0 (iPhone; CPU iPhone OS 14_7_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 yiban_OS/5.0")
                .send().await.with_context(|| format!("There is something wrong with sending or accepting response!!"))?;

            let headers = response.headers();
            yb_client.progress = LoginProgress::Verify(headers.get("Location").unwrap().to_str().with_context(
                || format!("header中没有Location参数")
            )?.to_string());
            Ok(yb_client)
        }
        _ => Err(anyhow!("必须是Verify过程"))
    };
}

